jetcrab\vm\executor\instruction_handlers/
comparison.rs

1//! # Comparison Handler
2//!
3//! Handles all comparison and logical operations in the VM including equality checks,
4//! relational comparisons, and logical operations.
5//!
6//! ## Operations Supported
7//!
8//! - **Equality**: equal, not_equal, strict_equal, strict_not_equal
9//! - **Relational**: less_than, less_equal, greater_than, greater_equal
10//! - **Logical**: logical_and, logical_or, logical_not
11//! - **Bitwise**: bitwise_and, bitwise_or, bitwise_xor, bitwise_not
12//!
13//! ## Comparison Rules
14//!
15//! - **Loose Equality**: Uses JavaScript-like type coercion
16//! - **Strict Equality**: No type coercion, exact value comparison
17//! - **Relational**: Numeric comparison with type coercion
18//! - **Logical**: Boolean operations with truthy/falsy conversion
19//!
20//! ## Usage
21//!
22//! ```rust
23//! use jetcrab::vm::executor::instruction_handlers::ComparisonHandler;
24//! use jetcrab::vm::executor::traits::StackOperations;
25//!
26//! let mut stack = MyStack::new();
27//! stack.push(Value::Number(5.0));
28//! stack.push(Value::Number(3.0));
29//! ComparisonHandler::greater_than(&mut stack)?;
30//! // Stack now contains: [true]
31//! ```
32
33use crate::vm::executor::error_handler::ExecutionError;
34use crate::vm::executor::traits::StackOperations;
35use crate::vm::value::Value;
36
37/// Handles comparison and logical operations for the VM
38pub struct ComparisonHandler;
39
40impl ComparisonHandler {
41    /// Checks if two values are equal using loose equality
42    ///
43    /// Pops two values from the stack, compares them using JavaScript-like
44    /// type coercion, and pushes the boolean result.
45    ///
46    /// # Arguments
47    /// * `stack` - The stack to operate on
48    ///
49    /// # Returns
50    /// * `Ok(())` on success
51    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
52    ///
53    /// # Examples
54    ///
55    /// ```rust
56    /// let mut stack = MyStack::new();
57    /// stack.push(Value::Number(5.0));
58    /// stack.push(Value::String("5".to_string()));
59    /// ComparisonHandler::equal(&mut stack)?;
60    /// assert_eq!(stack.pop(), Some(Value::Boolean(true)));
61    /// ```
62    pub fn equal<S>(stack: &mut S) -> Result<(), ExecutionError>
63    where
64        S: StackOperations,
65    {
66        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
67        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
68
69        let result = match (a, b) {
70            (Value::Number(a), Value::Number(b)) => a == b,
71            (Value::String(a), Value::String(b)) => a == b,
72            (Value::Boolean(a), Value::Boolean(b)) => a == b,
73            (Value::Null, Value::Null) => true,
74            (Value::Undefined, Value::Undefined) => true,
75            (Value::Number(a), Value::String(b)) => a.to_string() == b,
76            (Value::String(a), Value::Number(b)) => a == b.to_string(),
77            (Value::Number(a), Value::Boolean(b)) => a == if b { 1.0 } else { 0.0 },
78            (Value::Boolean(a), Value::Number(b)) => (if a { 1.0 } else { 0.0 }) == b,
79            _ => false,
80        };
81
82        stack.push(Value::Boolean(result));
83        Ok(())
84    }
85
86    /// Checks if two values are not equal using loose equality
87    ///
88    /// Pops two values from the stack, compares them using JavaScript-like
89    /// type coercion, and pushes the boolean result.
90    ///
91    /// # Arguments
92    /// * `stack` - The stack to operate on
93    ///
94    /// # Returns
95    /// * `Ok(())` on success
96    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
97    pub fn not_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
98    where
99        S: StackOperations,
100    {
101        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
102        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
103
104        let result = match (a, b) {
105            (Value::Number(a), Value::Number(b)) => a != b,
106            (Value::String(a), Value::String(b)) => a != b,
107            (Value::Boolean(a), Value::Boolean(b)) => a != b,
108            (Value::Null, Value::Null) => false,
109            (Value::Undefined, Value::Undefined) => false,
110            (Value::Number(a), Value::String(b)) => a.to_string() != b,
111            (Value::String(a), Value::Number(b)) => a != b.to_string(),
112            (Value::Number(a), Value::Boolean(b)) => a != if b { 1.0 } else { 0.0 },
113            (Value::Boolean(a), Value::Number(b)) => (if a { 1.0 } else { 0.0 }) != b,
114            _ => true,
115        };
116
117        stack.push(Value::Boolean(result));
118        Ok(())
119    }
120
121    /// Checks if two values are strictly equal
122    ///
123    /// Pops two values from the stack, compares them without type coercion,
124    /// and pushes the boolean result.
125    ///
126    /// # Arguments
127    /// * `stack` - The stack to operate on
128    ///
129    /// # Returns
130    /// * `Ok(())` on success
131    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
132    pub fn strict_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
133    where
134        S: StackOperations,
135    {
136        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
137        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
138
139        let result = a == b;
140        stack.push(Value::Boolean(result));
141        Ok(())
142    }
143
144    /// Checks if two values are strictly not equal
145    ///
146    /// Pops two values from the stack, compares them without type coercion,
147    /// and pushes the boolean result.
148    ///
149    /// # Arguments
150    /// * `stack` - The stack to operate on
151    ///
152    /// # Returns
153    /// * `Ok(())` on success
154    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
155    pub fn strict_not_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
156    where
157        S: StackOperations,
158    {
159        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
160        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
161
162        let result = a != b;
163        stack.push(Value::Boolean(result));
164        Ok(())
165    }
166
167    /// Checks if the first value is less than the second
168    ///
169    /// Pops two values from the stack, compares them, and pushes the boolean result.
170    /// Supports numeric comparison with type coercion.
171    ///
172    /// # Arguments
173    /// * `stack` - The stack to operate on
174    ///
175    /// # Returns
176    /// * `Ok(())` on success
177    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
178    pub fn less_than<S>(stack: &mut S) -> Result<(), ExecutionError>
179    where
180        S: StackOperations,
181    {
182        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
183        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
184
185        let result = match (a, b) {
186            (Value::Number(a), Value::Number(b)) => a < b,
187            (Value::String(a), Value::String(b)) => a < b,
188            (Value::Number(a), Value::String(b)) => a < b.parse::<f64>().unwrap_or(f64::NAN),
189            (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) < b,
190            _ => false,
191        };
192
193        stack.push(Value::Boolean(result));
194        Ok(())
195    }
196
197    /// Checks if the first value is less than or equal to the second
198    ///
199    /// Pops two values from the stack, compares them, and pushes the boolean result.
200    /// Supports numeric comparison with type coercion.
201    ///
202    /// # Arguments
203    /// * `stack` - The stack to operate on
204    ///
205    /// # Returns
206    /// * `Ok(())` on success
207    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
208    pub fn less_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
209    where
210        S: StackOperations,
211    {
212        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
213        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
214
215        let result = match (a, b) {
216            (Value::Number(a), Value::Number(b)) => a <= b,
217            (Value::String(a), Value::String(b)) => a <= b,
218            (Value::Number(a), Value::String(b)) => a <= b.parse::<f64>().unwrap_or(f64::NAN),
219            (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) <= b,
220            _ => false,
221        };
222
223        stack.push(Value::Boolean(result));
224        Ok(())
225    }
226
227    /// Checks if the first value is greater than the second
228    ///
229    /// Pops two values from the stack, compares them, and pushes the boolean result.
230    /// Supports numeric comparison with type coercion.
231    ///
232    /// # Arguments
233    /// * `stack` - The stack to operate on
234    ///
235    /// # Returns
236    /// * `Ok(())` on success
237    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
238    pub fn greater_than<S>(stack: &mut S) -> Result<(), ExecutionError>
239    where
240        S: StackOperations,
241    {
242        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
243        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
244
245        let result = match (a, b) {
246            (Value::Number(a), Value::Number(b)) => a > b,
247            (Value::String(a), Value::String(b)) => a > b,
248            (Value::Number(a), Value::String(b)) => a > b.parse::<f64>().unwrap_or(f64::NAN),
249            (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) > b,
250            _ => false,
251        };
252
253        stack.push(Value::Boolean(result));
254        Ok(())
255    }
256
257    /// Checks if the first value is greater than or equal to the second
258    ///
259    /// Pops two values from the stack, compares them, and pushes the boolean result.
260    /// Supports numeric comparison with type coercion.
261    ///
262    /// # Arguments
263    /// * `stack` - The stack to operate on
264    ///
265    /// # Returns
266    /// * `Ok(())` on success
267    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
268    pub fn greater_equal<S>(stack: &mut S) -> Result<(), ExecutionError>
269    where
270        S: StackOperations,
271    {
272        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
273        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
274
275        let result = match (a, b) {
276            (Value::Number(a), Value::Number(b)) => a >= b,
277            (Value::String(a), Value::String(b)) => a >= b,
278            (Value::Number(a), Value::String(b)) => a >= b.parse::<f64>().unwrap_or(f64::NAN),
279            (Value::String(a), Value::Number(b)) => a.parse::<f64>().unwrap_or(f64::NAN) >= b,
280            _ => false,
281        };
282
283        stack.push(Value::Boolean(result));
284        Ok(())
285    }
286
287    /// Performs logical AND operation
288    ///
289    /// Pops two values from the stack, performs logical AND, and pushes the result.
290    /// Uses JavaScript-like truthy/falsy conversion.
291    ///
292    /// # Arguments
293    /// * `stack` - The stack to operate on
294    ///
295    /// # Returns
296    /// * `Ok(())` on success
297    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
298    pub fn logical_and<S>(stack: &mut S) -> Result<(), ExecutionError>
299    where
300        S: StackOperations,
301    {
302        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
303        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
304
305        let a_truthy = is_truthy(&a);
306        let b_truthy = is_truthy(&b);
307
308        let result = a_truthy && b_truthy;
309        stack.push(Value::Boolean(result));
310        Ok(())
311    }
312
313    /// Performs logical OR operation
314    ///
315    /// Pops two values from the stack, performs logical OR, and pushes the result.
316    /// Uses JavaScript-like truthy/falsy conversion.
317    ///
318    /// # Arguments
319    /// * `stack` - The stack to operate on
320    ///
321    /// # Returns
322    /// * `Ok(())` on success
323    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
324    pub fn logical_or<S>(stack: &mut S) -> Result<(), ExecutionError>
325    where
326        S: StackOperations,
327    {
328        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
329        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
330
331        let a_truthy = is_truthy(&a);
332        let b_truthy = is_truthy(&b);
333
334        let result = a_truthy || b_truthy;
335        stack.push(Value::Boolean(result));
336        Ok(())
337    }
338
339    /// Performs logical NOT operation
340    ///
341    /// Pops one value from the stack, performs logical NOT, and pushes the result.
342    /// Uses JavaScript-like truthy/falsy conversion.
343    ///
344    /// # Arguments
345    /// * `stack` - The stack to operate on
346    ///
347    /// # Returns
348    /// * `Ok(())` on success
349    /// * `Err(ExecutionError::StackUnderflow)` if stack is empty
350    pub fn logical_not<S>(stack: &mut S) -> Result<(), ExecutionError>
351    where
352        S: StackOperations,
353    {
354        let value = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
355        let result = !is_truthy(&value);
356        stack.push(Value::Boolean(result));
357        Ok(())
358    }
359
360    /// Performs bitwise AND operation
361    ///
362    /// Pops two values from the stack, performs bitwise AND, and pushes the result.
363    /// Converts values to integers before operation.
364    ///
365    /// # Arguments
366    /// * `stack` - The stack to operate on
367    ///
368    /// # Returns
369    /// * `Ok(())` on success
370    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
371    pub fn bitwise_and<S>(stack: &mut S) -> Result<(), ExecutionError>
372    where
373        S: StackOperations,
374    {
375        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
376        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
377
378        let result = match (a, b) {
379            (Value::Number(a), Value::Number(b)) => {
380                Value::Number((a as i64 & b as i64) as f64)
381            }
382            _ => return Err(ExecutionError::TypeError("Cannot perform bitwise AND on non-numeric values".to_string())),
383        };
384
385        stack.push(result);
386        Ok(())
387    }
388
389    /// Performs bitwise OR operation
390    ///
391    /// Pops two values from the stack, performs bitwise OR, and pushes the result.
392    /// Converts values to integers before operation.
393    ///
394    /// # Arguments
395    /// * `stack` - The stack to operate on
396    ///
397    /// # Returns
398    /// * `Ok(())` on success
399    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
400    pub fn bitwise_or<S>(stack: &mut S) -> Result<(), ExecutionError>
401    where
402        S: StackOperations,
403    {
404        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
405        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
406
407        let result = match (a, b) {
408            (Value::Number(a), Value::Number(b)) => {
409                Value::Number((a as i64 | b as i64) as f64)
410            }
411            _ => return Err(ExecutionError::TypeError("Cannot perform bitwise OR on non-numeric values".to_string())),
412        };
413
414        stack.push(result);
415        Ok(())
416    }
417
418    /// Performs bitwise XOR operation
419    ///
420    /// Pops two values from the stack, performs bitwise XOR, and pushes the result.
421    /// Converts values to integers before operation.
422    ///
423    /// # Arguments
424    /// * `stack` - The stack to operate on
425    ///
426    /// # Returns
427    /// * `Ok(())` on success
428    /// * `Err(ExecutionError::StackUnderflow)` if insufficient operands
429    pub fn bitwise_xor<S>(stack: &mut S) -> Result<(), ExecutionError>
430    where
431        S: StackOperations,
432    {
433        let b = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
434        let a = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
435
436        let result = match (a, b) {
437            (Value::Number(a), Value::Number(b)) => {
438                Value::Number((a as i64 ^ b as i64) as f64)
439            }
440            _ => return Err(ExecutionError::TypeError("Cannot perform bitwise XOR on non-numeric values".to_string())),
441        };
442
443        stack.push(result);
444        Ok(())
445    }
446
447    /// Performs bitwise NOT operation
448    ///
449    /// Pops one value from the stack, performs bitwise NOT, and pushes the result.
450    /// Converts value to integer before operation.
451    ///
452    /// # Arguments
453    /// * `stack` - The stack to operate on
454    ///
455    /// # Returns
456    /// * `Ok(())` on success
457    /// * `Err(ExecutionError::StackUnderflow)` if stack is empty
458    pub fn bitwise_not<S>(stack: &mut S) -> Result<(), ExecutionError>
459    where
460        S: StackOperations,
461    {
462        let value = stack.pop().ok_or(ExecutionError::StackUnderflow)?;
463
464        let result = match value {
465            Value::Number(n) => Value::Number(!(n as i64) as f64),
466            _ => return Err(ExecutionError::TypeError("Cannot perform bitwise NOT on non-numeric value".to_string())),
467        };
468
469        stack.push(result);
470        Ok(())
471    }
472}
473
474/// Determines if a value is truthy according to JavaScript rules
475///
476/// # Arguments
477/// * `value` - The value to check
478///
479/// # Returns
480/// * `true` if the value is truthy
481/// * `false` if the value is falsy
482fn is_truthy(value: &Value) -> bool {
483    match value {
484        Value::Boolean(b) => *b,
485        Value::Number(n) => *n != 0.0 && !n.is_nan(),
486        Value::String(s) => !s.is_empty(),
487        Value::Null => false,
488        Value::Undefined => false,
489        Value::Object(_) => true,
490        Value::Array(_) => true,
491        Value::Function(_) => true,
492    }
493}